package com.nervenets.general.service.impl;

import com.nervenets.general.service.RedisService;
import com.nervenets.general.utils.StringUtils;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Objects;
import java.util.function.Supplier;

@Service
public class RedisServiceImpl implements RedisService {
    private static final int DEFAULT_EXPIRED_SECONDS = 300;
    private static final String _OPERATION_CUSTOM_INTERVAL_KEY_ = "PROCEED";
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    public Boolean setNx(String key, String value) {
        return setNx(key, value, DEFAULT_EXPIRED_SECONDS);
    }

    @Override
    public Boolean setNx(String key, String value, int expire) {
        return redisTemplate.execute((RedisCallback<Boolean>) rc -> {
            final Boolean r = rc.setNX(key.getBytes(), value.getBytes());
            rc.expire(key.getBytes(), expire > 0 ? expire : DEFAULT_EXPIRED_SECONDS);
            return r;
        });
    }

    public Long delete(String key) {
        return redisTemplate.execute((RedisCallback<Long>) rc -> rc.del(key.getBytes()));
    }

    public Boolean setExpire(String key, int expire) {
        return redisTemplate.execute((RedisCallback<Boolean>) rc -> rc.expire(key.getBytes(), expire > 0 ? expire : DEFAULT_EXPIRED_SECONDS));
    }

    public Long incrBy(String key, long offset) {
        return redisTemplate.execute((RedisCallback<Long>) rc -> rc.incrBy(key.getBytes(), offset));
    }

    public Long incr(String key) {
        return redisTemplate.execute((RedisCallback<Long>) rc -> rc.incr(key.getBytes()));
    }

    public Boolean hset(String key, String member, String value) {
        return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> redisConnection.hSet(key.getBytes(), member.getBytes(), value.getBytes()));
    }

    public Boolean set(String key, String value) {
        return redisTemplate.execute((RedisCallback<Boolean>) rc -> rc.set(key.getBytes(), value.getBytes()));
    }

    public Boolean set(String key, String value, int expire) {
        return redisTemplate.execute((RedisCallback<Boolean>) rc ->
                rc.set(key.getBytes(), value.getBytes(), Expiration.seconds(expire > 0 ? expire : DEFAULT_EXPIRED_SECONDS), RedisStringCommands.SetOption.UPSERT));
    }

    public String get(String key) {
        if (!hasKey(key)) return null;
        return redisTemplate.execute((RedisCallback<String>) rc -> {
            final byte[] bytes = rc.get(key.getBytes());
            if (null == bytes) return null;
            return new String(bytes);
        });
    }

    public String hget(String key, String member) {
        return redisTemplate.execute((RedisCallback<String>) rc -> {
            if (hexists(key, member)) {
                return new String(Objects.requireNonNull(rc.hGet(key.getBytes(), member.getBytes())));
            }
            return null;
        });
    }

    public Long hdel(String key, String member) {
        return redisTemplate.execute((RedisCallback<Long>) rc -> rc.hDel(key.getBytes(), member.getBytes()));
    }

    public Long getExpire(String key) {
        return redisTemplate.execute((RedisCallback<Long>) rc -> rc.ttl(key.getBytes()));
    }

    public Boolean canProceedCustom(String key, int expire) {
        String lockKey = StringUtils.formatKey(_OPERATION_CUSTOM_INTERVAL_KEY_, key);
        return setNx(lockKey, "1", expire);
    }

    public void clearRedis() {
        redisTemplate.execute((RedisCallback<Boolean>) rc -> {
            rc.flushDb();
            return true;
        });
    }

    @Override
    public String cache(String cacheKey, Supplier<String> executor) {
        return cache(cacheKey, executor, 0);
    }

    @Override
    public String cache(String cacheKey, Supplier<String> executor, int expire) {
        if (hasKey(cacheKey)) return this.get(cacheKey);
        String result = executor.get();
        if (expire > 0)
            this.set(cacheKey, result, expire);
        else
            this.set(cacheKey, result);
        return result;
    }

    @Override
    public Boolean hasKey(String key) {
        return redisTemplate.execute((RedisCallback<Boolean>) rc -> rc.exists(key.getBytes()));
    }

    @Override
    public Boolean hexists(String key, String member) {
        return redisTemplate.execute((RedisCallback<Boolean>) rc -> rc.hExists(key.getBytes(), member.getBytes()));
    }
}
